package com.sailing.xjpb.orm.intercepter;

import com.google.common.collect.Lists;
import com.sailing.xjpb.common.SailingAssert;
import com.sailing.xjpb.common.SailingReflectionUtils;
import com.sailing.xjpb.common.operator.SailingTenant;
import com.sailing.xjpb.common.sercurity.SailingUserContext;
import com.sailing.xjpb.orm.intercepter.annotation.DbCreateTime;
import com.sailing.xjpb.orm.intercepter.annotation.DbUpdateTime;
import com.sailing.xjpb.orm.repository.SailingRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.GenericTypeResolver;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.util.StopWatch;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * repository的动态代理类，执行save之前绑定当前用户信息
 *
 * @author YaoWei createDate 2020年10月29日 下午12:47:57
 * @see org.springframework.data.repository.CrudRepository
 * @see CrudRepositoryBeanPostProcessor
 */
@Slf4j
public class CrudRepositoryHandler implements InvocationHandler {
    /**
     * CrudRepository#save
     */
    public static final String METHOD_SAVE = "save";
    /**
     * CrudRepository#saveAll
     */
    public static final String METHOD_SAVE_ALL = "saveAll";
    /**
     * 用户上下文
     */
    private static SailingUserContext sailingUserContext;
    /**
     * 被代理的CrudRepository
     */
    private Object crudRepository;
    /**
     * 用户上下文
     */
    private static SailingRepository sailingRepository;

    private static final List<CrudRepositoryOperatorHandler> operatorHandlers = Lists.newArrayList();

    /**
     * 设置用户上下文信息
     *
     * @param sailingUserContext 用户上下文
     */
    public static void setUserContext(SailingUserContext sailingUserContext) {
        if (sailingUserContext != null) {
            CrudRepositoryHandler.sailingUserContext = sailingUserContext;
        }
    }

    /**
     * 添加操作人处理器
     *
     * @param handler 操作人处理器
     */
    public static void addOperatorHandler(CrudRepositoryOperatorHandler handler) {
        operatorHandlers.add(handler);
    }

    /**
     * 添加SailingRepository
     */
    public static void setSailingRepository(SailingRepository sailingRepository) {
        if (sailingRepository != null) {
            CrudRepositoryHandler.sailingRepository = sailingRepository;
        }
    }

    /**
     * 绑定对象，并生成代理对象
     *
     * @param target 被代理的对象(the new bean instance)
     * @return 代理后的对象
     * @author YaoWei
     * createDate: Date 2018年9月11日 下午4:48:31
     */
    public Object proxyInstance(Object target) {
        if (target instanceof CrudRepository) {
            this.crudRepository = target;
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            return Proxy.newProxyInstance(classLoader, target.getClass().getInterfaces(), this);
        }
        return target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        boolean isSaveMethod = (METHOD_SAVE.equals(methodName) || METHOD_SAVE_ALL.equals(methodName));
        //处理保存方法
        if (isSaveMethod && args[0] != null) {
            if (log.isDebugEnabled()) {
                log.debug("RepositoryHandler ==> 正在动态代理类{}的save|saveAll方法 ", proxy.getClass());
            }
            StopWatch clock = new StopWatch("实体保存");
            clock.start("保存之前设置操作人信息");
            // 设置操作人信息
            this.setOperator(args[0]);
            clock.stop();
            if (log.isDebugEnabled()) {
                log.debug(clock.prettyPrint());
            }
            return method.invoke(crudRepository, args);
        }
        //处理统计、查询、删除方法
        Class<?> entityClass = this.getEntityClass(proxy.getClass());
        boolean isXtTenantImplClass = SailingTenant.class.isAssignableFrom(entityClass);
        if (isXtTenantImplClass && !method.isAnnotationPresent(Query.class)) {
            SailingCrudRepositoryContext.setSqlTenantCodeFlag(Boolean.TRUE);
        } else {
            SailingCrudRepositoryContext.setSqlTenantCodeFlag(Boolean.FALSE);
        }
        return method.invoke(crudRepository, args);
    }

    /**
     * 设置用户信息
     *
     * @param entity 实体对象
     * @author YaoWei
     * createDate: 2020年11月19日 下午12:46:33
     * editAuthor: YaoWei editDate 2020年11月19日 下午12:46:33
     */
    public void setOperator(Object entity) {
        if (entity instanceof Collection) {
            this.setOperator((Collection<?>) entity);
        } else {
            this.setOperator(Collections.singletonList(entity));
        }
    }

    /**
     * 设置操作信息-包括用户和数据库时间
     *
     * @param entities 实体对象
     * @author YaoWei createDate 2020年11月19日 下午12:46:33
     * editAuthor: YaoWei editDate 2020年11月19日 下午12:46:33
     */
    public void setOperator(Collection<?> entities) {
        Date dbTime = null;
        for (Object entity : entities) {
            if (dbTime == null) {
                dbTime = getDbTimeIfNecessary(entity);
            }
            for (CrudRepositoryOperatorHandler h : operatorHandlers) {
                boolean newEntity = h.isNewEntity(entity);
                h.setOperator(entity, newEntity, sailingUserContext);
                if (dbTime != null) {
                    h.setDbTime(entity, newEntity, dbTime);
                }
            }
        }
    }

    /**
     * 获取实体类型
     *
     * @param repositoryClass 实体类字节码
     * @return 实体类型
     */
    private synchronized Class<?> getEntityClass(Class<?> repositoryClass) {
        Class<?> entityClass = GenericTypeResolver.resolveTypeArgument(repositoryClass, JpaSpecificationExecutor.class);
        SailingAssert.notNull(entityClass, "repository支持的实体类不能为空!");
        return entityClass;
    }

    /**
     * 根据实体是否需要获取数据库时间
     * 检查实体中是否有字段被 {@link DbCreateTime}和 {@link DbUpdateTime} 注解标记
     * 如果有，则调用 {@link SailingRepository#getDbTime()} 获取当前数据库时间
     *
     * @param entity 实体对象，用于检查是否需要获取数据库时间
     * @return 如果实体中存在被 {@link DbCreateTime} 和 {@link DbUpdateTime}  注解标记的字段，则返回数据库时间；否则返回 null
     */
    private Date getDbTimeIfNecessary(Object entity) {
        // 获取实体类的类类型
        Class<?> entityClazz = entity.getClass();
        // 获取实体类中所有被 DbCreateTime和DbUpdateTime注解标记的字段
        List<Field> fields = SailingReflectionUtils.getDeclaredFields(entityClazz,
                f -> f.isAnnotationPresent(DbUpdateTime.class) || f.isAnnotationPresent(DbCreateTime.class));
        if (fields.isEmpty()) {
            return null;
        }
        // 返回当前数据库时间
        return sailingRepository.getDbTime();
    }
}
